package dao

import (
	"context"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"fmt"
	"os"

	"github.com/go-redis/redis/v8"
	"github.com/google/wire"
	"github.com/nsqio/go-nsq"
	"github.com/qiniu/go-sdk/v7/auth/qbox"
	"github.com/qiniu/go-sdk/v7/storage"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"

	"open/internal/conf"
	"open/pkg/alipay"
	"open/pkg/bytedance"
	"open/pkg/orm"
	wechat "gitee.com/quantum-force/wxopen-api"
)

// ProviderSet is dao providers.
var ProviderSet = wire.NewSet(NewDao)

// Dao 访问基础设施
type Dao struct {
	// db
	db    *gorm.DB
	cache *redis.Client

	// mq
	producer  *nsq.Producer
	consumers []*nsq.Consumer

	// platform
	wechat    *wechat.Client
	alipay    *alipay.Client
	bytedance *bytedance.Client

	// conf
	conf *conf.Dao
}

// NewDao 初始化基础设置
func NewDao(conf *conf.Dao) (*Dao, error) {
	db, err := orm.NewPostgreSQL(&orm.Config{
		DSN:         conf.Orm.Dsn,
		Active:      int(conf.Orm.Active),
		Idle:        int(conf.Orm.Idle),
		IdleTimeout: conf.Orm.IdleTimeout.AsDuration(),
		NamingStrategy: schema.NamingStrategy{
			TablePrefix:   conf.Orm.NamingStrategy.TablePrefix,
			SingularTable: conf.Orm.NamingStrategy.SingularTable,
		},
	})
	if err != nil {
		return nil, err
	}
	cache := redis.NewClient(&redis.Options{
		Addr:         conf.Redis.Addr,
		Password:     conf.Redis.Password,
		DB:           int(conf.Redis.Db),
		DialTimeout:  conf.Redis.DialTimeout.AsDuration(),
		WriteTimeout: conf.Redis.WriteTimeout.AsDuration(),
		ReadTimeout:  conf.Redis.ReadTimeout.AsDuration(),
	})
	var nsqAddress string
	if address, ok := os.LookupEnv("NSQD_ADDRESS"); ok {
		nsqAddress = address
	} else {
		nsqAddress = conf.Nsq.NsqAddress
	}
	nsqConf := nsq.NewConfig()
	nsqConf.MaxAttempts = uint16(conf.Nsq.MaxAttempts)
	nsqConf.MsgTimeout = conf.Nsq.MsgTimeout.AsDuration()
	nsqConf.MaxRequeueDelay = conf.Nsq.MaxRequeueDelay.AsDuration()
	nsqConf.DefaultRequeueDelay = conf.Nsq.DefaultRequeueDelay.AsDuration()
	nsqConf.MaxInFlight = int(conf.Nsq.MaxInFlight)
	producer, err := nsq.NewProducer(nsqAddress, nsqConf)
	if err != nil {
		return nil, err
	}

	// 初始化支付宝客户端
	encodedKey, err := base64.StdEncoding.DecodeString(conf.Open.Alipay.PrivateKey)
	if err != nil {
		return nil, err
	}
	privateKey, err := x509.ParsePKCS8PrivateKey(encodedKey)
	if err != nil {
		return nil, err
	}
	encodedKey, err = base64.StdEncoding.DecodeString(conf.Open.Alipay.PublicKey)
	if err != nil {
		return nil, err
	}
	publicKey, err := x509.ParsePKIXPublicKey(encodedKey)
	if err != nil {
		return nil, err
	}

	return &Dao{
		db:       db,
		cache:    cache,
		producer: producer,
		wechat:   wechat.NewClient(nil),
		alipay: alipay.NewClient(nil,
			privateKey.(*rsa.PrivateKey),
			publicKey.(*rsa.PublicKey),
			conf.Open.Alipay.AesKey,
			alipay.AppID(conf.Open.Alipay.AppId),
			alipay.SignType(conf.Open.Alipay.SignType)),
		bytedance: bytedance.NewClient(),
		conf:      conf,
	}, nil
}

// FromContext 从上下文获取DB
func (d *Dao) FromContext(ctx context.Context) *gorm.DB {
	db, ok := ctx.Value(transactionKey).(*gorm.DB)
	if !ok {
		db = d.db.WithContext(ctx)
	}
	return db.WithContext(ctx)
}

// GetQiNiuUploadToken 获取七牛token
func (d *Dao) GetQiNiuUploadToken(bucket string, key string) string {
	mac := qbox.NewMac(d.conf.Qn.AccessKey, d.conf.Qn.SecretKey)
	putPolicy := storage.PutPolicy{
		Scope: fmt.Sprintf("%s:%s", bucket, key),
	}
	return putPolicy.UploadToken(mac)
}
